grammar smallCGrammar;

options {
  language = Java;
}

@header {
  package compilers.grammar;
  import compilers.AST.Node;
  import compilers.AST.basics.*;
  import compilers.AST.expression.*;
  import compilers.AST.statement.*;
  import compilers.AST.types.*;
  import compilers.symbolTable.*;
  import compilers.AST.Tree;
  import compilers.compiler.Compiler;
  import compilers.exceptions.SyntacticalException;
  import compilers.exceptions.SemanticalException;
  import java.util.LinkedList;
  import java.util.Iterator;
}

@lexer::header {
  package compilers.grammar;
  import compilers.*;
}

@members {
  Tree tree = new Tree();
  SymbolTable symbolTable = new SymbolTable();
  SymbolTableScope currentScope = symbolTable.getRoot();
  SyntacticalException syntaxException = null;
  SemanticalException semanticalException = null;
  
  public void displayRecognitionError(String[] tokenNames, RecognitionException e) {
    if (syntaxException == null) {
      syntaxException = new SyntacticalException(e);
    }
  }
}

program returns [Compiler compiler]
  : 
  { Program program = new Program(); }
  ( 
  declarationStatement {program.addStatement($declarationStatement.statement);}
  | includeStatement {program.addStatement($includeStatement.statement);}
  | functionDeclStatement {program.addStatement($functionDeclStatement.statement);}
  | functionDefStatement {program.addStatement($functionDefStatement.statement);}
  | typedefStatement {program.addStatement($typedefStatement.statement);}
  )+
  {
    tree.addRoot(program);
    $compiler = new Compiler(tree, symbolTable, syntaxException, semanticalException);
  }
  ;
  
statement returns [Statement statement]
  :
  ( 
  assignmentStatement {statement = $assignmentStatement.statement;}
  | declarationStatement {statement = $declarationStatement.statement;}
  | conditionStatement {statement = $conditionStatement.statement;}
  | loopStatement {statement = $loopStatement.statement;}
  | branchStatement {statement = $branchStatement.statement;}
  | returnStatement {statement = $returnStatement.statement;}
  | typedefStatement {statement = $typedefStatement.statement;}
  | functionCallStatement {statement = $functionCallStatement.statement;}
  | functionDeclStatement {statement = $functionDeclStatement.statement;}
  | functionDefStatement {statement = $functionDefStatement.statement;}
  )
  ;
  
assignmentStatement returns [Statement statement]
  : 
  ( 
  id1=IDENT '=' expr1=expression ';'
  {
    VariableAssignmentStatement statm = new VariableAssignmentStatement($id1.getLine());
    statm.setLValue(new Identifier($id1.text, $id1.getLine()));
    statm.setRValue($expr1.expression);
    $statement = statm;
  }
  | arrayTerm equals='=' expr2=expression ';'
  {
    ArrayTermAssignmentStatement statm = new ArrayTermAssignmentStatement($equals.getLine()); 
    statm.setLValue($arrayTerm.term);
    statm.setRValue($expr2.expression);
    $statement = statm;
  }
  | id3=IDENT '=' arrayValList ';'
  {
    ArrayListAssignmentStatement statm = new ArrayListAssignmentStatement($id3.getLine()); 
    statm.setLValue(new Identifier($id3.text, $id3.getLine()));
    statm.setRValue($arrayValList.arValList);
    $statement = statm;
  }
  )
  ;
  
declarationStatement returns [Statement statement]
  : 
  ( 
  variableDecl { $statement = $variableDecl.statement; }
  | arrayVariableDecl { $statement = $arrayVariableDecl.statement; }
  | constantDecl { $statement = $constantDecl.statement; }
  | constantArrayDecl { $statement = $constantArrayDecl.statement; }
  ) ';'
  ; 

variableDecl returns [VariableDeclarationStatement statement]
 : typeSpecifier id1=IDENT 
 {
   $statement = new VariableDeclarationStatement($id1.getLine());
   $statement.setType($typeSpecifier.type);
   $statement.setIdentifier(new Identifier($id1.text, $id1.getLine()));
   SymbolTableVariableElement element = new SymbolTableVariableElement($id1.text, $typeSpecifier.type, $statement.getNodeId(), $id1.getLine());
   try {
     currentScope.addElement(element);
   } catch (SemanticalException e) {
     semanticalException = e;
   }
 }
 ('=' expression {$statement.setRValue($expression.expression);})?
 ;
 
arrayVariableDecl returns [ArrayDeclarationStatement statement]
 : typeSpecifier id1=IDENT '[' expr1=expression ']'
 {
   $statement = new ArrayDeclarationStatement($id1.getLine());
   $statement.setType($typeSpecifier.type);
   $statement.setIdentifier(new Identifier($id1.text, $id1.getLine()));
   $statement.setLength($expr1.expression);
   SymbolTableArrayElement element = new SymbolTableArrayElement($id1.text, $typeSpecifier.type, $statement.getNodeId(), $id1.getLine());
   try {
    currentScope.addElement(element);
   } catch (SemanticalException e) {
    semanticalException = e;
   }
 }
 ('=' arrayValList {$statement.setRValue($arrayValList.arValList);})?
 ;
  
constantDecl returns [VariableDeclarationStatement statement]
 : 'const' typeSpecifier id1=IDENT '=' expression 
 {
   $statement = new VariableDeclarationStatement($id1.getLine());
   $statement.setType($typeSpecifier.type);
   $statement.setIdentifier(new Identifier($id1.text, $id1.getLine()));
   $statement.makeConstant();
   $statement.setRValue($expression.expression);
   SymbolTableVariableElement element = new SymbolTableVariableElement($id1.text, $typeSpecifier.type, true, $statement.getNodeId(), $id1.getLine());
   try {
    currentScope.addElement(element);
   } catch (SemanticalException e) {
    semanticalException = e;
   }
 }
 ;
  
constantArrayDecl returns [ArrayDeclarationStatement statement]
 : 'const' typeSpecifier id1=IDENT '[' expr1=expression ']' '=' arrayValList
 {
   $statement = new ArrayDeclarationStatement($id1.getLine());
   $statement.setType($typeSpecifier.type);
   $statement.setIdentifier(new Identifier($id1.text, $id1.getLine()));
   $statement.makeConstant();
   $statement.setLength($expr1.expression);
   $statement.setRValue($arrayValList.arValList);
   SymbolTableArrayElement element = new SymbolTableArrayElement($id1.text, $typeSpecifier.type, true, $statement.getNodeId(), $id1.getLine());
   try {
    currentScope.addElement(element);
   } catch (SemanticalException e) {
    semanticalException = e;
   }
 }
 ;

arrayValList returns [ArrayValueList arValList]
  : 
  brac='{' 
  {
    $arValList = new ArrayValueList($brac.getLine());
  }
  (exp1=expression {$arValList.addExpression($exp1.expression);}) 
  (',' exp2=expression {$arValList.addExpression($exp2.expression);})* 
  '}'
  ;

conditionStatement returns [ConditionStatement statement]
  : ifToken='if' expr1=expression '{' 
  {
    $statement = new ConditionStatement($ifToken.getLine());
    $statement.setExpression($expr1.expression);
    SymbolTableScope ifScope = new SymbolTableScope(currentScope, $statement);
    currentScope = ifScope;
  } 
  (stmnt1=statement { $statement.addIfStatement($stmnt1.statement); } )* 
  '}'
  ( 'else' '{' 
    {
      currentScope = currentScope.getParent();
      SymbolTableScope elseScope = new SymbolTableScope(currentScope, $statement);
      currentScope = elseScope;
      $statement.setElse();
    }
    (stmnt2=statement { $statement.addElseStatement($stmnt2.statement); } )* '}'
  )?
  { currentScope = currentScope.getParent();}
  ;
  
loopStatement returns [LoopStatement statement]
  : whi='while' expression '{'
  { 
    $statement = new LoopStatement($whi.getLine());
    SymbolTableScope whileScope = new SymbolTableScope(currentScope, $statement);
    currentScope = whileScope;
    $statement.setExpression($expression.expression);
  } 
  (stat1=statement { $statement.addStatement($stat1.statement); } )* '}'
  { currentScope = currentScope.getParent();}
  ;
  
branchStatement returns [BranchStatement statement]
  : 
  ( br='break' ';' {$statement = new BranchStatement(BranchStatement.BREAK, $br.getLine());}
  | cont='continue' ';' {$statement = new BranchStatement(BranchStatement.CONTINUE, $cont.getLine());}
  )
  ;
  
includeStatement returns [IncludeStatement statement]
  : inc='#include <stdio.h>'
  { 
    $statement = new IncludeStatement($inc.getLine());
    SymbolTableIncludeElement element = new SymbolTableIncludeElement($statement.getNodeId(), $inc.getLine());
    try {
      currentScope.addElement(element);
    } catch (SemanticalException e) {
      semanticalException = e;
    }
  } 
  ;
  
returnStatement returns [ReturnStatement statement]
  : ret='return' expression ';'
  { 
    $statement = new ReturnStatement($ret.getLine());
    $statement.setExpression($expression.expression);
  } 
  ;

typedefStatement returns [TypeDefStatement statement]
  : def='typedef' typeSpecifier
  { 
    $statement = new TypeDefStatement($def.getLine()); 
    $statement.setType($typeSpecifier.type);
  }
  // TODO Array typedefs
  //( '[' ']' { $statement.makeArray(); array = true; })? 
  id=IDENT ';'{ 
    $statement.setIdentifier(new Identifier($id.text, $id.getLine()));
    SymbolTableTypeElement element = new SymbolTableTypeElement($id.text, $typeSpecifier.type, $statement.getNodeId(), $id.getLine());
    try {
      currentScope.addElement(element);
    } catch (SemanticalException e) {
      semanticalException = e;
    }
  }
  ;

functionCallTerm returns [FunctionCallTerm term, int line]
  : printf
  {
    $line = $printf.line;
    $term = $printf.term;
  }
  | scanf
  {
    $line = $scanf.line;
    $term = $scanf.term;
  }
  | id=IDENT 
  {
    $line = $id.getLine();
    $term = new FunctionCallTerm($id.getLine());
    $term.setIdentifier(new Identifier($id.text, $id.getLine())); 
  }   
  '(' 
  (expr1=expression { $term.addParameter($expr1.expression); }
  (',' expr2=expression { $term.addParameter($expr2.expression); } )* 
  )?
  ')'
  ;
  
printf returns [PrintFunctionCallTerm term, int line]
  : prf='printf' '('  charAr=CHARARRAY
  {
    $line = prf.getLine();
    $term = new PrintFunctionCallTerm($charAr.text, $line);
  } 
  (',' expression {$term.addParameter($expression.expression);} )* 
  ')'
  ;
  
scanf returns [ScanFunctionCallTerm term, int line]
  : scf='scanf' '(' charAr=CHARARRAY 
  {
    $line = scf.getLine();
    $term = new ScanFunctionCallTerm($charAr.text, $line);
  } 
  (',' term2=term {$term.addParameter($term2.term);} )* 
  ')'
  ;
  
functionCallStatement returns [FunctionCallStatement statement]
  : functionCallTerm tok=';'
  {$statement = new FunctionCallStatement($functionCallTerm.term, $tok.getLine());}
  ;

functionDeclStatement returns [FunctionDeclarationStatement statement]
  :
  typeSpecifier id1=IDENT '('
  { 
    $statement = new FunctionDeclarationStatement($id1.getLine());
    $statement.setReturnType($typeSpecifier.type);
    $statement.setIdentifier(new Identifier($id1.text, $id1.getLine()));
  }
  (
    parameter1=param
    {
      Parameter param1 = $parameter1.param;
    }
    (
    id2=IDENT 
    { 
      param1.setIdentifier(new Identifier($id2.text, $id2.getLine()));
    }
    )?
    {
      $statement.addParameter(param1);
    } 
    (
    ',' parameter2=param 
    {
      Parameter param2 = $parameter2.param;
    }
    ( 
    id3=IDENT 
    {
      param2.setIdentifier(new Identifier($id3.text, $id3.getLine()));
    }
    )?
    {
      $statement.addParameter(param2);
    }
    )*
  )?
  ')' ';'
  {
      SymbolTableFunctionElement element = new SymbolTableFunctionElement($id1.text, $typeSpecifier.type, $statement.getParameters(), false, $statement.getNodeId(), $id1.getLine());
      try {
        currentScope.addElement(element);
      } catch (SemanticalException e) {
        semanticalException = e;
      }
  }
  ;
  
functionDefStatement returns [FunctionDefinitionStatement statement]
  :
  typeSpecifier id1=IDENT '('
  { 
    $statement = new FunctionDefinitionStatement($id1.getLine());
    $statement.setReturnType($typeSpecifier.type);
    $statement.setIdentifier(new Identifier($id1.text));
  }
  (
    parameter1=param { $statement.addParameter($parameter1.param);}
    (
    ',' parameter2=param {$statement.addParameter($parameter2.param);}
    )*
  )?
  ')' '{'
  {
    SymbolTableFunctionElement element = new SymbolTableFunctionElement($id1.text, $typeSpecifier.type, $statement.getParameters(), true, $statement.getNodeId(), $id1.getLine());
    try {
      currentScope.addFunctionDefinition(element, currentScope);
      SymbolTableScope newScope = new SymbolTableScope(currentScope, $statement, true);
      newScope.setIdent($id1.text);
      currentScope = newScope;
      if (!$statement.getParameters().isEmpty()) {
          Iterator<Parameter> it = $statement.getParameters().iterator();
          while (it.hasNext()) {
            currentScope.addElement(it.next().toSymbolTableElement());
          }
        }
    } catch (SemanticalException e) {
      semanticalException = e;
    }
  }
  (
  stat1=statement 
  {
    $statement.addStatement($stat1.statement);
  }
  )*
  '}'
  { currentScope = currentScope.getParent();}
  ;


param returns [Parameter param]
  : 
  (
    t1=typeSpecifier id1=IDENT
    {
      VariableParameter parameter = new VariableParameter($t1.line);
      parameter.setType($t1.type);
      parameter.setIdentifier(new Identifier($id1.text));
      $param = parameter;
    }
    | 'const' t2=typeSpecifier id2=IDENT 
    {
      VariableParameter parameter = new VariableParameter($t2.line);
      parameter.setType($t2.type);
      parameter.setIdentifier(new Identifier($id2.text));
      parameter.makeConstant();
      $param = parameter;
    }
  )
  ;

typeSpecifier returns [TypeSpecifier type, int line]
  :
  ( c='char' { $type = new TypeSpecifier(TypeSpecifier.CHAR, $c.getLine()); $line=$c.getLine(); }
  | i='int' { $type = new TypeSpecifier(TypeSpecifier.INTEGER, $i.getLine()); $line=$i.getLine(); }
  | f='float' { $type = new TypeSpecifier(TypeSpecifier.FLOAT, $f.getLine()); $line=$f.getLine(); }
  | v='void' { $type = new TypeSpecifier(TypeSpecifier.VOID, $v.getLine()); $line=$v.getLine(); }
  | id=IDENT { $type = new TypeSpecifier($id.text, $id.getLine()); $line=$id.getLine(); }
  )
  ;
  
expression returns [Expression expression]
  : log=logical
  { 
    $expression = new Expression($log.line);
    $expression.setLogical($log.logical);
  }
  ;

logical returns [Logical logical, int line]
  : rel1=relation
  { 
    $line = $rel1.line;
    $logical = new Logical($line);
    $logical.setFirstRelation($rel1.relation);
  }
    (
      (
      '&&' {$logical.addOperator(Logical.AND);} 
      | '||' {$logical.addOperator(Logical.OR);}
      ) rel2=relation { $logical.addSecondRelation($rel2.relation); } 
    )*
  ;
  
relation returns [Relation relation, int line]
  : sum1=sum 
  { 
    $line = $sum1.line;
    $relation = new Relation($line);
    $relation.setFirstSum($sum1.sum); 
  }
  (
    ( '==' { $relation.setComparator(Relation.EQUALS); }
    | '!=' { $relation.setComparator(Relation.NEQUALS); }
    | '<' { $relation.setComparator(Relation.LESS); }
    | '<=' { $relation.setComparator(Relation.LESSEQ); }
    | '>=' { $relation.setComparator(Relation.GREATEREQ); }
    | '>' { $relation.setComparator(Relation.GREATER); }
    ) sum2=sum { $relation.setSecondSum($sum2.sum); }
  )?
  ;
  
sum returns [Sum sum, int line]
  : m1=mult 
  { 
    $line = $m1.line;
    $sum = new Sum($line);
    $sum.setFirstMult($m1.mult);
  } 
  (
    (
    '+' {$sum.addOperator(Sum.PLUS);} 
    | '-' {$sum.addOperator(Sum.MINUS);}
    ) m2=mult { $sum.addSecondMult($m2.mult); } 
  )*
  ;
  
mult returns [Mult mult, int line]
  : u1=unary
  {
    $line = $u1.line;
    $mult = new Mult($line);
    $mult.setFirstUnary($u1.unary);
  }
  (
    ( '*' { $mult.addOperator(Mult.MULT); }
    | '/' { $mult.addOperator(Mult.DIV); }
    ) u2=unary { $mult.addSecondUnary($u2.unary); } 
  )*
  ;
  
unary returns [Unary unary, int line]
  : { $unary = new Unary(); } 
  (
    '+' {$unary.setSign(Unary.PLUS);} 
    | '-' {$unary.setSign(Unary.MINUS);} 
  )?
  negation 
  {
    $unary.setNegation($negation.negation);
    $line = $negation.line;
    $unary.setLine($line);
  }
  ;
  
negation returns [Negation negation, int line]
  : 
  { 
    $negation = new Negation(); 
    int nrNegations=0; 
  } 
  ('!' {nrNegations++;} )* 
  term 
  {
    $line = $term.line;
    $negation.setNrNegations(nrNegations); 
    $negation.setTerm($term.term);
    $negation.setLine($line); 
  }
  ;
  
term returns [Term term, int line] 
  : 
  {
    TypeSpecifier typeSpec = null;
  }
  ('(' typeSpecifier ')' {typeSpec = $typeSpecifier.type;} )?
  ( 
  id1=IDENT 
  {
    $line = $id1.getLine();
    $term = new Identifier($id1.text, $id1.getLine());
  }
  | arrayTerm { $term = $arrayTerm.term; $line = $arrayTerm.line; }
  | CHAR { $line = $CHAR.getLine(); $term = new MyChar($CHAR.text, $line); }
  | INT { $line = $INT.getLine(); $term = new MyInt($INT.text, $line); }
  | FLOAT { $line = $FLOAT.getLine(); $term = new MyFloat($FLOAT.text, $line); }
  | functionCallTerm { $line = $functionCallTerm.line; $term = $functionCallTerm.term; }
  | b='(' expr2=expression ')' { $line = $b.getLine(); $term = $expr2.expression; }
  )
  {
    if(typeSpec != null) {
      $term.setConvertTo(typeSpec);
    }
  }
  ;
  
arrayTerm returns [ArrayTerm term, int line]
  : id=IDENT '[' expression ']'
  {
    $line = $id.getLine();
    $term = new ArrayTerm($line);
    $term.setIdentifier(new Identifier($id.text, $line));
    $term.setIndex($expression.expression);
  }
  ;

CHAR: '\''.'\'';
CHARARRAY : '"' (.)* '"';
INT: ('0'..'9')+;
FLOAT : ('0'..'9')+('.'('0'..'9')+)?;
IDENT : ('a'..'z' | 'A'..'Z')('a'..'z' | 'A'..'Z' | '0'..'9')*; 
WHITESPACE : ( ' ' | '\t' | '\n' | '\r' | '\f')* {$channel = HIDDEN;};
COMMENT : '//' .* ('\n' | '\r') {$channel = HIDDEN;};
MULTILINECOMMENT  : '/*' .* '*/' {$channel = HIDDEN;};





